Für den Begriff Softwarearchitektur gibt es eine Reihe von Definitionen. Eine der gängigsten und gleichzeitig eingängigsten ist, Softwarearchitektur als Summe aller wichtigen Entscheidungen zu verstehen [1]. In agilen Teams werden diese Entscheidungen regelmäßig von den Teammitgliedern getroffen, ohne dass es ihnen zwangsläufig bewusst ist, dass sie mit jeder Entscheidung ihre Softwarearchitektur weiterentwickeln. Dedizierte Softwarearchitekten können dieses Wissen zwar einbringen, sind aber nicht in allen Fällen konstanter Teil des Teams und können daher gerade in agilen Projekten die Softwarearchitektur nicht einfach von oben herab durchsetzen [1]. Das Wissen um Softwarearchitektur muss also im Team verankert und aus dem Team heraus vorangetrieben werden. Denn unter suboptimalen oder gar schlechten Architekturentscheidungen leidet das Team am meisten selbst, zum Beispiel wenn die Softwareentwicklung wegen vieler Bugs oder schlechter Wartbarkeit immer zäher wird und es dadurch immer weniger Spaß macht, das System weiterzuentwickeln. Die Effizienz, und damit die Velocity des Teams, nimmt darauffolgend stark ab.
Schauen wir auf drei reale Szenarien, auf die wir als Softwarearchitekten immer wieder in Projekten treffen:
-
In einem Projekt, das seit einigen Jahren läuft und von Team „Aquädukt“ betreut wird, wird jedes neue Feature teurer und die geplante Auslieferung von Arbeitsergebnissen verzögert sich ein ums andere Mal. Der Kunde wird nervös. Deshalb beauftragt die Projektleitung eine externe Architekturanalyse. Als Ergebnis der Analyse werden Vorschläge zur Verbesserung der Architektur festgehalten. Team „Aquädukt“ hat jedoch in der Folge keine Zeit, diese Vorschläge auch umzusetzen, da neue Funktionalitäten Vorrang von Refactorings haben und der Zeitplan sowieso schon gerissen wurde. Zudem nimmt das Team die externen Ratschläge nicht mit dem notwendigen Ernst wahr. Nach einem Jahr wird die externe Analyse erneut durchgeführt, mit dem Ergebnis, dass nicht nur keine Verbesserung zu erkennen ist, sondern dass die Softwarearchitektur sich sogar verschlechtert hat. Der Kunde droht schließlich damit, das Projekt ganz einzustellen.
-
Team „Hinkelstein“ arbeitet schon seit geraumer Zeit an seinem Softwaresystem. Jeder beteiligte Entwickler hat im Laufe der Zeit seine Handschrift hinterlassen. Das Projekt hat auf den ersten Blick keine definierte Struktur und bei neuen Funktionen ist meist unklar, wohin sie technisch und fachlich gehören – ein klassischer Monolith. Die Abdeckung durch Tests ist schwach, zyklische Abhängigkeiten durchziehen den Code und manche Bereiche in der Software werden nur mit äußerster Vorsicht angefasst. Team „Hinkelstein“ möchte diese Situation gern ändern, es fehlt ihm jedoch die Erfahrung, sinnvolle Verbesserungen vorzunehmen. So bleiben auch hier erkannte Schwachstellen in der Software enthalten und werden nicht zeitnah und konsequent behoben. Der Zustand dieses Systems erodiert im Laufe der Zeit stetig. Schließlich finden sich immer weniger neue Mitarbeiter, die im Projekt arbeiten wollen, während langjährige Teammitglieder frustriert das Projekt verlassen oder in den wohlverdienten Ruhestand gehen.
-
Bei Team „Microcosmus“ scheint alles toll zu laufen. Es hat das System in fachliche Domänen aufgeteilt und diese als Microservices implementiert. Der Code wird in der Build-Pipeline automatisch einer umfangreichen Analyse unterzogen – alle Werte sind im grünen Bereich. Trotzdem bleibt ein mulmiges Gefühl, das sich keiner im Team so recht erklären kann. Änderungen an einem Microservice führen oft dazu, dass auch andere Microservices angepasst werden müssen. Innerhalb eines Microservice ist so viel Code entstanden, dass trotz der scheinbar guten Codequalität die Übersicht verloren geht. Kleine Änderungen ziehen damit öfter ein größeres Refactoring diverser Stellen im Code nach sich. Das Team ist ratlos, was es tun soll, um wieder Sicherheit und Vertrauen in die eigene Arbeit zu gewinnen.
Wie lassen sich solche Szenarien minimieren, die für alle Beteiligten zu Frust und im schlimmsten Fall wie bei Team „Aquädukt“ zum Abbruch eines Softwareprojekts führen? Was hätten die Teams tun können, um gar nicht erst in eine solche Situation zu geraten? Ähnlich wie in der Qualitätssicherung können Gamification-Techniken helfen, das Team im Bereich der Softwarearchitektur zu unterstützen, gute Softwarearchitektur zu erkennen, die eigenen Entscheidungen kritisch zu hinterfragen und Verbesserungen einzubringen. Ein wesentlicher Aspekt ist zudem die nachhaltige Stärkung des Teams als Ganzes und die Steigerung der Motivation jedes Einzelnen im Team, wenn alle zur Verbesserung der Software beitragen.
Wir stellen im Folgenden eine spielerische Möglichkeit vor, die eigene Software im Team zu evaluieren, dabei gemeinsam Probleme und Verbesserungspotential zu erkennen und beschlossene Verbesserungen anschließend auch umzusetzen. Das Spiel umfasst drei Phasen (Abb. 1), bei denen wir im Folgenden unseren drei Teams über die Schulter schauen werden.
Lust auf mehr DDD?
Workshops zu API Design, Software Analytics und zukunftssicherer Modellierung für die sofortige Anwendung in Ihren eigenen Projekten.
Phase 1: vom Problem- zum Lösungsraum
So unterschiedlich die Probleme der Teams in den beschriebenen Szenarien sind, so ähnlich kann ein Vorgehen sein, um diese zu identifizieren und potenzielle Lösungen abzuleiten. Softwareentwickler neigen oft dazu, sofort zu handeln, ohne das Problem wirklich durchdrungen zu haben. Bisher stand das Handeln bei allen Teams im Vordergrund, was erst die aktuellen Probleme verursacht hat. Die wichtigste Regel im Spiel lautet deshalb: „Erkenne zuerst das Problem, dann plane die Lösung, dann setze sie um.“
Doch wie startet man nun? Wenn die Erkenntnis gereift ist, dass es Probleme mit dem Softwaresystem gibt, speziell mit der Softwarearchitektur, dann ist der nächste Schritt, diese Probleme zu konkretisieren und zu benennen. Team „Aquädukt“ hat bereits konkrete Hinweise auf die bestehenden Probleme durch die externen Gutachter erhalten. Team „Hinkelstein“ und „Microcosmus“ stehen noch ganz am Anfang, wobei die Ausgangslage unterschiedlich ist. Team „Hinkelstein“ steht vor einem Monolithen und der Herausforderung, diesen besser bzw. überhaupt einmal zu strukturieren und aufzuteilen. Team „Microcosmus“ muss sich damit auseinandersetzen, warum das Vertrauen in das System gesunken ist. Was können diese beiden Teams also tun?
Wie wäre es mit einem Spielkartenset, das ähnlich wie die Karten beim Risk Storming [2] das Team bei der Problemanalyse und der Lösungsfindung leitet? Eine Orientierung bieten z. B. die Domain-Driven Refactorings, die von Henning Schwentner [3] zusammengetragen wurden. Diese bilden die Quelle unserer Spielkarten (Abb. 2). Sie umfassen jeweils eine Motivation – also das zu lösende Problem – und schlagen eine Lösung vor, die auf den Best Practices aus dem Domain-Driven Design basiert.
Werfen wir einen Blick auf Team „Hinkelstein“, das vor einem Monolithen steht, den Handlungsbedarf erkennt, aber nicht weiß, wo es anfangen soll. Welche Karte(n) würde es ziehen? Vielleicht:
-
Extract Bounded Context: Das Team erkennt, dass sich die Fachlichkeit nicht adäquat im System abbildet. Mehrere unterschiedliche Karten adressieren das Problem. Als Lösung schlägt dieses Refactoring das Herausschälen eines Bounded Contexts aus dem Monolithen vor. Das Team entscheidet sich nach einiger Diskussion für diese Variante.
-
Enforce Ubiquitous Language: Diese Karte wird von einem neuen Mitglied im Team ausgespielt. Die alten Hasen sind so an die technischen Begriffe im Code gewöhnt, dass sie die Diskrepanz zur Fachlichkeit nicht mehr erkennen. Das neue Teammitglied ist aber immer unsicher, welche Stellen im Code für die angeforderten Änderungen von der Fachseite zu bearbeiten sind. Als Lösung wird das Umbenennen im Code, aber auch in der Datenbank, anhand der fachlichen Begriffe gefordert.
-
Form Cross-Functional Team: Eine weitere Karte, die gespielt wird, betrifft gar nicht technische, sondern soziotechnische Aspekte. Es stellt sich die Frage, wer im Team sich um den herausfaktorierten Bounded Context kümmern soll. Da sich die Teamstruktur nach Conway’s Law immer auch auf die Software auswirkt und ein erneutes Zusammenwachsen der Softwareteile befürchtet wird, wird auch aus dem bisher großen Team ein kleineres Team herausgelöst. Dieses kümmert sich um die Weiterentwicklung des jetzt unabhängigen Softwareteils.
Auch für Team „Microcosmus“ können die Refactorings neue Lösungen für ihre Probleme anbieten, die das Team selbst nicht im Blick hatte. Diese Refactorings sind im Gegensatz zu denen, die Team „Hinkelstein“ anwendet, sehr viel näher an der Implementierung – schließlich hat dieses Team die Domäne bereits sinnvoll in der Software abgebildet. Beim genauen Hinsehen sehen die Entities aber doch sehr anämisch aus und viel Logik ist in Services statt den Entities selbst implementiert. Das Team wendet also die folgenden Refactorings an:
-
Move Logic from Service to Entity: Die Logik wird so weit wie möglich in den Entities gekapselt.
-
Replace Method Call with Domain Event: Zudem stellt das Team anhand dieser Karte fest, dass die Kopplung zwischen den Microservices viel zu eng ist, da diese sich gegenseitig direkt aufrufen. Das Team beschließt deshalb, stattdessen Domain Events zu schicken, um die Microservices unabhängiger voneinander weiterentwickeln zu können.
Das Vorgehen sollte zunächst also die Ebene der Probleme betrachten. Liegen diese wie bei Team „Hinkelstein“ eher auf der strategischen und soziotechnischen Ebene oder sind diese wie bei Team „Microcosmus“ eher taktischer Natur? Je nachdem können die passenden Refactorings ausgewählt und angewendet werden. Natürlich können weitere Karten mit Best Practices und auch aus anderen Quellen hinzugenommen werden, für den Start sind die vorgestellten Refactorings aber ein guter Ausgangspunkt, da sie alle diese Aspekte abdecken.
Nach der ersten Phase des Spiels wissen die Teams nun, wo die Probleme liegen und auf welche Weise sie zu beheben sind – doch wie verhindern sie, dass die Umsetzung im Alltag untergeht, ein Problem, das insbesondere auch Team „Aquädukt“ betrifft?
Phase 2: nichts liegenlassen
Wer kennt es nicht – die guten Vorsätze aus dem letzten Jahr kommen auch dieses Jahr wieder auf den Tisch, weil man es einfach nicht geschafft hat, sie in der Realität umzusetzen. So kann es unseren drei Teams auch ergehen. Aus Phase 1 sind eine Reihe von abstrakten Vorschlägen für Refactorings und Verbesserungen auf verschiedenen Ebenen gesammelt worden, die nun konkretisiert werden müssen, damit sie überhaupt umsetzbar sind. Wichtig ist in erster Linie, sich nicht alles auf einmal vorzunehmen, sondern kleine Schritte zum Ziel zu definieren. Was bedeutet das für die einzelnen Teams?
Team „Aquädukt“ muss die Vorschläge aus der Softwarearchitekturanalyse in umsetzbare Pakete herunterbrechen. Team „Hinkelstein“ muss für die Umsetzung zunächst mit den Fachexperten die Domäne neu erkunden, was dank Methoden des Collaborativ Modeling sehr viel Spaß bringen kann und auch die Fachabteilung wieder enger an das Team bindet, allerdings erst einmal eingeplant und organisiert werden muss. Team „Microcosmus“ muss die vorgeschlagenen Refactorings auf den Code abbilden und dann möglichst Codestelle für Codestelle und Aufruf für Aufruf abarbeiten. Aber was soll mit welcher Priorität angegangen werden?
Wir empfehlen hier, in Anlehnung an Maturity Poker [4], die vorgeschlagenen Refactorings als Ziele zu nutzen und diese in ähnlicher Weise wie dort in eine Matrix einzutragen (Abb. 3). Für jedes abstrakte Refactoring werden die konkreten Ziele definiert. Dies kann auch iterativ erfolgen, meist ergeben sich im Laufe der Zeit neue Ziele. So wird Team „Hinkelstein“ sicher im Laufe des Umbaus des zuvor monolithischen Systems auch taktische Refactorings vornehmen, diese jedoch im ersten Schritt noch nicht berücksichtigen. Das Team stimmt anschließend über den Reifegrad der Ziele ab, also ob diese bereits direkt umsetzbar sind oder noch weiter verfeinert werden müssen, sowie die Priorität, mit der das Ziel umgesetzt werden soll. Die Ziele mit der höchsten Priorität werden anschließend so weit verfeinert, dass sie direkt eingeplant werden können. Der Schritt soll die Einigkeit im Team stärken sowie einen gemeinsamen Konsens erzeugen, dass die geplanten Maßnahmen wichtig und durchführbar sind.
Um die Umsetzung zu steuern, müssen die konkreten Refactorings noch in das Ticketsystem des Teams, sei es ein virtuelles oder z.B. ein reales Board überführt werden. Hierzu schlagen Carola Lilienthal und Henning Schwentner zwei Möglichkeiten vor: das separate Führen eines eigenen Backlogs nur für diese Tickets oder das Integrieren in das normale Product Backlog [5]. Je nach Wahl wird dann neben dem eigentlichen Board noch ein weiteres Board gepflegt. Dies hat allerdings den Nachteil, dass das Team zwei Boards im Blick behalten muss – nach unserer Erfahrung ist das eine Konstellation, die ein Team eher ablehnt und die zu weniger Transparenz führt, wer gerade an was arbeitet, und inwieweit z.B. ein Sprintziel erreicht wurde. Alternativ kann ein eigener Tickettyp oder eine Swimlane im normalen Backlog bzw. Board verwendet werden. Wir nennen das Sammeln der Refactoring-Tickets der Einfachheit halber „Improvement Backlog“, ein Begriff aus aim42 [6], unabhängig davon, wo die Tickets physisch abgelegt werden.
In unserem Beispiel wurde das Refactoring „Move Logic from Service to Entity“ in der Pokerrunde von Team „Hinkelstein“ als wichtiger empfunden als „Replace Method Call with Domain Event“. Es legt für jedes Entity, das aus seinem anämischen Zustand in einen angereicherten Zustand überführt werden soll, ein Ticket in diesem Improvement-Backlog an. Um das zweite geplante Refactoring nicht zu vergessen, wird ein Epic dafür angelegt, ohne jedoch bereits konkrete Task abzuleiten.
Die Phase ist mit der Definition und Einplanung umsetzbarer Refactorings abgeschlossen, aber die notwendige Voraussetzung für die nun folgende Challenge.
Phase 3: Wer gewinnt?
Am Ende geht es bei spielerischen Ansätzen immer auch um eine Challenge. Dafür braucht es natürlich ein Maß, an dem sich die Verbesserungen darstellen lassen und das den Fortschritt sichtbar macht. Für die unterschiedlichen Teams können dabei unterschiedliche Maße herangezogen werden.
Für Team „Microcosmus“ bietet sich das Improvement Backlog als Maß an, das dieses ja bereits in Phase 2 gefüllt hat. Wir empfehlen, explizit ein solches Improvement Board zu pflegen und kontinuierlich die Tickets aus diesem Board in die Entwicklung einzuplanen. Gemessen wird dann, wie viele Improvements aus diesem Board und damit wie viele sogenannte „Improvement Points“ umgesetzt wurden. Improvement Points können dabei entweder wie Story Points nach Komplexität geschätzt werden oder einfach statisch mit dem immer gleichen Wert versehen werden. Dieses Maß hat auch den Vorteil, dass es den Fortschritt schnell und transparent für alle Stakeholder sichtbar macht. Durch zeitnahe Auswertungen steigt die Motivation, nicht ausschließlich neue Funktionalität zu implementieren, sondern auch aktiv Verbesserungen einzuplanen. Die Teammitglieder spielen dabei gegeneinander. Pro Sprint gewinnt das Teammitglied, das die beste Balance zwischen Improvement Points und Story Points geschafft und damit am meisten zur Verbesserung der Softwarearchitektur und zur Weiterentwicklung des Systems beigetragen hat. So soll verhindert werden, dass sich Teammitglieder ausschließlich auf das Improvement Board fokussieren.
Jedes Team entscheidet selbst, wie diese Balance zwischen den Points ausgestaltet wird. Während die Teams „Aquädukt“ und „Hinkelstein“ höheren Bedarf an Refactorings haben, deshalb vermutlich mehr Improvement Points einplanen und damit höher gewichten, muss Team „Microcosmus“ deutlich weniger Kraft für die Refactorings aufwenden. Ein Beispiel für eine Auswertung zeigt Abb. 4 – typisch ist, dass die Improvement Points in der Regel einen geringeren Umfang als die Story Points einnehmen, aber trotzdem kontinuierlich eingeplant und abgearbeitet werden.
Das langfristige Ziel für Teams ist die Verbesserung des Ergebnisses bei der nächstfolgenden Softwarearchitekturanalyse. Carola Lilienthal [7] schlägt zur Bewertung von Softwaresystemen beispielsweise den Modularity Maturity Index (MMI) vor. Das Team hat bei der Analyse den MMI für das eigene System erfahren und möchte diesen um mindestens eine festgelegte Zahl von Punkten verbessern. Es stellt sich damit in den – virtuellen – Wettbewerb mit anderen Systemen, die ebenfalls mit dem MMI bewertet wurden. Dieses Ziel ist zwar potenziell messbar, zur Erreichung sollten jedoch kleinere Teilziele angestrebt werden, um das Team nicht mit einem großen Ziel zu frustrieren. Die kleinen Teilschritte führen automatisch zur Erreichung des größeren Ziels zu. Die vorgeschlagenen Verbesserungen aus der Softwarearchitekturanalyse können als Basis für ein Improvement Backlog dienen und dort so weit verfeinert werden, dass sie für das Team in kleinen Schritten umsetzbar sind.
Auch Team „Hinkelstein“ nutzt die Improvement Points zur Steuerung. Natürlich könnte es als Maß einfach die Reports der statischen Codeanalyse nutzen, die das System aus verschiedenen Aspekten wie Code Coverage oder zyklischen Abhängigkeiten mit ein oder mehreren Zahlen versehen. Das ist in diesem Fall jedoch nicht das richtige Maß, da das Team vermutlich scheitern wird – die Verbesserung auch nur eines Maßes, wie z. B. die Test Coverage, erfordert eine große Anstrengung. Den Monolithen in ein gut wartbares System zu verwandeln, ist ein Marathon, kein Sprint. Deshalb strebt das Team auch hier kleinere Ziele an, die sich schnell erreichen lassen, um Frust vorzubeugen. In Phase 2 hat das Team eine Menge geschafft: Mit den Fachexperten wurde das System in fachliche Domänen aufgeteilt, die Teamstruktur an diese Domänen angepasst und ein grober Plan für das Refactoring erarbeitet. Dieser grobe Plan kann wiederum verfeinert werden und in das Improvement Backlog einfließen. Die neuen kleineren Teams können gut gegeneinander spielen. So kann nach jeder Iteration gemessen werden, welches Team die größte Verbesserung anhand der Tickets im Improvement-Backlog erreicht hat und damit am meisten zur erfolgreichen Entflechtung des Monolithen beigetragen hat.
Letztendlich gewinnen alle Projektbeteiligten. Das Team bekommt einen Weg gezeigt, wie es aus der Falle, der sich immer höher aufhäufenden technischen Schulden entkommt. Die normale Entwicklung pausiert nicht, das heißt, das Team wird weiterhin durch den glücklichen Product Owner oder Endnutzer positiv bestärkt, der durch das „normale“ Backlog bedient wird. Der spielerische Ansatz sorgt auf der anderen Seite dafür, dass notwendige Umbauten und Verbesserungen nicht liegenbleiben. Wenn Improvements und Refactorings einen deutlich höheren Stellenwert bekommen und außerdem für die Challenge genutzt werden, steigt die Motivation, diese aktiv anzugehen – als Team gegen andere Teams oder als Teammitglied im Wettbewerb mit den anderen Teammitgliedern. Das Team gewinnt die Sicherheit zurück, dass Anpassungen und Weiterentwicklungen möglich sind, ohne dass es Angst haben muss, dass wie beim Mikado der nächste Schritt das System zum Einsturz bringt. Die im Laufe der Zeit sichtbaren Verbesserungen der Softwarearchitektur tragen zudem dazu bei, eine qualitativ höherwertige Software auszuliefern, was zu wachsendem Erfolg und gestärktem Vertrauen beim Kunden führt und in der Rückkopplung wiederum zur Zufriedenheit im Team beiträgt.
IMMER AUF DEM NEUESTEN STAND BLEIBEN?
Dann abonnieren Sie unseren Newsletter und erhalten Sie Updates zum Event, Infos über neu erschienene Blogartikel und weitere Themen rund um Softwarearchitektur
Links & Literatur
[1] Wolff, Eberhard: „Software-Architektur: Worauf es ankommt – Grundlagen für Software-Architektur“: https://www.innoq.com/de/articles/2020/01/grundlagen-fuer-software-architektur/
[2] Sokenou, Dehla; Güldali, Baris: „Gamification in der Qualitätssicherung – Teil 3: Schach dem Risiko!“; in: Java Magazin 7.23
[3] Schwentner, Henning: „Domain-Driven Refactorings“; https://hschwentner.io/domain-driven-refactorings/
[4] Sokenou, Dehla; Güldali, Baris: „Gamification in der Qualitätssicherung – Teil 2: Poker um den Prozess!“; in: Java Magazin 5.23
[5] Lilienthal; Carola, Schwentner, Henning: „Domain-Driven Transformation. Monolithen und Microservices zukunftsfähig machen“; dpunkt.verlag, 2023
[6] Starke, Gernot: „aim42“: https://www.aim42.org
[7] Lilienthal, Carola: „Langlebige Software-Architekturen. Technische Schulden analysieren, begrenzen und abbauen“; dpunkt.verlag, 2019